package de.bht.fb6.cg1.exercise2;

import java.util.ArrayList;
import java.util.List;

import ij.IJ;
import ij.ImagePlus;
import ij.gui.GenericDialog;
import ij.gui.NewImage;
import ij.plugin.PlugIn;
import ij.process.ImageProcessor;

/**
 * This class represents a plug in for ImageJ. It scales an image.
 * 
 * @author Stephan Rehfeld <rehfeld (-at-) beuth-hochschule.de>
 */
public class ScaleImage implements PlugIn {

    /**
     * The ImagePlus object of the loaded image.
     */
    private final ImagePlus imp;

    /**
     * This main method stars ImageJ and applies this plug in to the loaded image.
     * 
     * @param args Ignored.
     */
    public static void main(final String args[]) {
        IJ.open("test.jpg");
        final ScaleImage scaleImage = new ScaleImage(IJ.getImage());
        scaleImage.run("");
    }

    /**
     * This constructor creates a new ScaleImage object.
     * 
     * @param imp
     *            The ImagePlus object. Must not be null!
     */
    public ScaleImage(final ImagePlus imp) {
        if (imp == null) {
            throw new IllegalArgumentException("The parameter 'imp' must not be null!");
        }
        this.imp = imp;
    }

    @Override
    public void run(final String arg) {
        imp.show();

        final String[] methods = {
                "Nearest-Neighbor", "Bilinear"
        };
        final GenericDialog gd = new GenericDialog("Scale");
        gd.addChoice("Sampling method", methods, methods[0]);
        gd.addNumericField("Width", 500, 0);
        gd.addNumericField("Height", 500, 0);
        gd.showDialog();

        final int method = gd.getNextChoiceIndex();
        final int width = (int) gd.getNextNumber();
        final int height = (int) gd.getNextNumber();

        final ImageProcessor ipOld = imp.getProcessor();
        final ImagePlus newImage = NewImage.createRGBImage("Skaliertes Bild", width, height, 1, NewImage.FILL_BLACK);

        final ImageProcessor ipNew = newImage.getProcessor();

        final int[] pixOld = (int[]) ipOld.getPixels();
        final int[] pixNew = (int[]) ipNew.getPixels();

        for (int i = 0; i < ipNew.getWidth(); ++i) {
            for (int j = 0; j < ipNew.getHeight(); ++j) {
                final int oldX = (i * ipOld.getWidth()) / ipNew.getWidth();
                final int oldY = (j * ipOld.getHeight()) / ipNew.getHeight();

                if (method == 0) {
                    pixNew[j * ipNew.getWidth() + i] = pixOld[oldY * ipOld.getWidth() + oldX];
                }
                if (method == 1) {
                    // Bilinear
                    
                    // get the modifier values for the pixels arround
                    double leftXModifier = ((i * ipOld.getWidth()) / ipNew.getWidth()) - oldX;
                    double rightXModifier = 1 - leftXModifier;
                    double leftYModifier = ((j * ipOld.getHeight()) / ipNew.getHeight()) - oldY;
                    double rightYModifier = 1 - leftYModifier;
                    ArrayList<Integer> values = new ArrayList<Integer>(5);
                    
                    // add the smaller point values
                    values.add(new Double(pixOld[oldY * ipOld.getWidth() + oldX] * leftXModifier).intValue());
                    values.add(new Double(pixOld[oldY * ipOld.getWidth() + oldX] * leftYModifier).intValue());

                    // add the greater point value
                    if (oldX + 1 < ipOld.getWidth()) {
                        values.add(new Double(pixOld[oldY * ipOld.getWidth() + oldX + 1] * rightXModifier).intValue());
                    }
                    if (oldY + 1 < ipOld.getHeight()) {
                        values.add(new Double(pixOld[(oldY + 1) * ipOld.getWidth() + oldX] * rightYModifier).intValue());
                    }

                    // calculate the middle value
                    pixNew[j * ipNew.getWidth() + i] = average(values);
                }
            }
        }
        newImage.show();
    }

    /**
     * get the average color value from a list of RGB integer values.
     * 
     * @param vals List of integer color values
     * @return average color value.
     */
    public int average(List<Integer> vals) {
        int[] r = new int[vals.size()];
        int[] g = new int[vals.size()];
        int[] b = new int[vals.size()];

        for (int i = 0; i < vals.size(); i++) {
            int point = vals.get(i);
            r[i] = (point & 0x00ff0000) >> 16;
            g[i] = (point & 0x0000ff00) >> 8;
            b[i] = point & 0x000000ff;
        }

        return (0xff << 24) | getMiddle(r) << 16 | getMiddle(g) << 8 | getMiddle(b);
    }

    /**
     * calculates the double of the middle value from a integer array.
     * 
     * @param vals integer values to calculate
     * @return result value
     */
    private int getMiddle(final int[] vals) {
        int result = 0;
        for (final int i : vals) {
            result += i;
        }
        return result / (vals.length / 2);
    }

}
